Copyright: Visual Help ® 1993-1994 Firas Bushnaq


Contents
SocketTools Samples

Each SocketTools sample program was written to highlight the most important features of any one control while providing a feature-rich application that can be used for its intended task. The samples were written in a very simple, straight-forward style for easy understanding. Situations are kept as general as possible so that the program code can be reused in your applications by cutting and pasting into your applications.

General Considerations

ST/FTP

ST/Mail

ST/E-Alarm

ST/Netcheck

ST/Newsreader

ST/Telnet

ST/Web

Internet Fantan


General
General Considerations

This section will describe some of the basic issues in using the Internet-related controls that are a part of SocketTools. This section covers such topics as establish connections, terminating connections, blocking vs. non-blocking sockets, and reading and writing with the socket. After reading through this section, you will have the information you need to perform some basic operations with all of the controls.

Establishing and Terminating Connections

Blocking vs. Non-Blocking Sockets

Reading and Writing with Sockets


Connections
Establishing and Terminating Connections

In almost all situations that require network development, especially the internet, the role of the client is to establish a connection with a server. An application that requires the services that is known to exist on another system, is always the client, and, as such, initiates the connection. A server provides a service for clients to facilitate their requests. Each server program out on the network sits and waits for clients to make that all important connection. Once connected, the client and server can communicate directly by what ever means has previously been established.

Using the phone metaphor, the client initiates the phone call to a remote system and asks if the server is home. If the server can take the call, the client and server can begin talking. A client cannot “call” a server unless it knows the phone number. Therefore, a connection can only be made if the client can specify the name of the host computer or its IP address. If a client has the right number, it then has to be sure the party is home. This is equivalent to knowing the servers port number. This is a number that uniquely identifies the server on any one system. Although it can really be any number, the Internet has standardized certain servers with well-known port numbers. As a result, each SocketTools components attempts connections based on a default port number that is the well-known port number for that particular protocol. Should the port number be different for some reason (possibly for security reasons), each of the controls has a RemotePort and RemoteService property that can be set prior to making the connection.

Each SocketTools control insulates you from the nitty-gritty details of making this connection, but, nonetheless, a connection needs to be established. Some controls require an explicit connection, while others make the connection implicitly. An explicit connection is one which requires you to assign the HostAddress or HostName property and then using the Action property with a value of Control_ACTION_CONNECT or calling the Control_Connect method, where Control is replaced by the acutal control's name (such as FTP, WEB, SMTP, etc.). An implicit connection is made when, during a network operation, a connection is automatically made. This is evidenced in the Web control when using the HTTP_ACTION_LOAD action to retrieve a document.

Establishing a connection with a server will necessitate the need to terminate the connection. Every interaction between a client and server can be described as follows:

1 	Establish a connection.

2 	Request and obtain the desired resources.

3 	Break or terminate the connection.

In all cases where a control requires an explicit connection, an explicit disconnection is also required. This is accomplished by either setting the Action property to Control_ACTION_DISCONNECT or calling the Control_Disconnect method, where Control is replaced by the control's name.

Of the eight controls that make TCP/IP socket connections with their server counterpart, five require explicit connection and disconnection actions. These are:

•	FTP (File Transfer Protocol) control

•	POP (Post Office Protocol) control

•	SMTP (Simple Mail Transfer Protocol) control

•	Telnet (Terminal) control

•	RLIB (Remote Execution) control

The RLIB control is the only one of the above controls that does not use the Control.Connect method or Control_ACTION_CONNECT Action property value. Since the RLIB control offers two ways in which a connection is established, there are two methods or Action property values. The methods are Rlib.Login and Rlib.Execute, and the Action properties are  RLIB_ACTION_LOGIN and RLIB_ACTION_EXECUTE. In both cases a connection is established with a server, but with a variation in their behavior.

The other four controls use the Control.Connect method or Control_ACTION_CONNECT Action property to begin the dialogue with the server. To make a successful connection, other properties require values before a connection can be established. For example, either the HostName or HostAddress property must contain a value before a connection can be attempted. Some of the controls may require the Username property and optionally the Password property to be set. You may also need to set the RemotePort or RemoteService property if they differ from their default values.

FTP Control:
  FtpClient1.HostName = ftp.funhouse.com
  FtpClient1.Username = anonymous
  FtpClient1.Password = kennyg@newwave.com
  FtpClient1.Connect or FtpClient1.Action = FTP_ACTION_CONNECT

POP Control:
  PopClient1.HostName = ms.royal.bp.uk
  PopClient1.Username = charles
  PopClient1.Password = cpb
  PopClient1.Connect or PopClient1.Action = POP_ACTION_CONNECT

SMTP Control
  SmtpClient1.HostName = mail.power.com
  SmtpClient1.Connect or SmtpClient1.Action = SMTP_ACTION_CONNECT

Telnet Control:
  Telnet1.HostName = s1.datasouth.com
  Telnet1.Connect or Telnet1.Action = TELNET_ACTION_CONNECT

Establishing connections with servers requires some understanding of the behaviour of that particular server. For example, some servers will automatically break a connection after so many minutes of inactivity. This is typical of most FTP servers. Therefore, the application needs to consider whether a connection will be “held” open for the duration of the application or whether, based on acitivities the application performs, connection and disconnections will be made when appropriate. It would be more appropriate and resourceful to do the latter. This is especially the case for FTP, POP and SMTP. For each of these controls, the desired activity can be performed between code that performs the connection and disconnection. The Telnet control is an example where a connection is created and held open until the user decides to close the application.


Blocking
Blocking vs. Non-Blocking Sockets

The decision to use blocking sockets or non-blocking sockets needs to be made based on how you are programming your application and, in some cases, a particular interaction with the server. Before describing the difference, each style is determined by the value of the Blocking property. The only TCP/IP controls that do not have a Blocking property are the DNS (Domain Name Server) control and the ICMP (or Ping) control.

Setting the Blocking property to a value of True will cause any method or Action property that requires data interaction with the server to not return until the interaction has been completed. This is best illustrated in controls that directly read data from the server, such as FTP, POP3 and Telnet.

Applications where blocking sockets would best be served are when an interaction with another process contain dialogue that is known and expected. For situations where data can arrive unexpectedly or the application requires interaction with the user during data transfers (possibly to cancel the operation), non-blocking sockets would serve best. Like in most programming situations there is no absolute right or wrong, but there usually is an appropriate method based on efficiency and the design of the overall application.

The first action that is affected by the state of the Blocking property is the CONNECT action. When Blocking is set to True, the statement that assigns the control's CONNECT constant to the Action property will not come back until the status of the connection is known. Setting the Blocking property to False causes the CONNECT action to immediately return and execute the next statement. Before any other socket actions are issued, you must check for the Connected property to insure that the connection has indeed been established.

When receiving data via a blocking socket, you must set the number of bytes that you want to receive. Although you may receive something less than what you ask for, you will always receive something (barring any error). This is the fundamental principle behind the blocking socket. If you are looking to receive data from the host by the blocking socket and no data is ready, execution is suspended until data is received. If you did not receive the expected amount of data, you can issue another request to receive more. Again, the request will block until some data appears on the socket. Here is a subroutine that performs a blocking read on a socket using the SocketWrench/VB control.

Sub SocketBlockRead (nbytes As Integer, buffer As String)
    Dim tmpbuffer as String

    buffer = ""
    Do
        Socket1.RecvLen = nbytes
        tmpbuffer = Socket1.RecvData
        nbytes = nbytes - Socket1.RecvLen
        buffer = buffer + tmpbuffer
    Loop Until nbytes = 0
End Sub

Note the setting of RecvLen before accessing the RecvData property to tell the socket control the maximum number of bytes that we want to receive. Also note the use of the RecvLen property after the blocking read of the socket using the RecvData property that tells us the actual number of bytes that were received into tmpbuffer. We could have using its length using the Len function, but the RecvLen property highlights the point. This code should only be used when you are expecting to receive the number of bytes (or characters) that you are requesting. This code will be suspended if the RecvData property is assigned when there is no more data to be read from the socket.

Sending data through a blocking socket follows a similar experience. Setting the SendData property will only return from the control when some data has successfully been written to the socket. The SendLen property only specifies a maximum number of bytes to send. After the SendData statement is executed, the SendLen property will tell you how many bytes were actually sent. The following subroutine issues a blocking write.

Sub SocketBlockWrite (nbytes As Integer, buffer As String)
    Do
        Socket1.SendLen = nbytes
        Socket1.SendData = Right$(buffer, nbytes)
        nbytes = nbytes - Socket1.SendLen
    Loop Until nbytes = 0
End Sub

For non-blocking sockets, when data appears on the socket which is ready to be received, a Read event is generated which will allow you to then execute the statement to receive the data (see the next section for more details and reading and writing with sockets). When the Blocking property is set to False, an attempt to receive data when no data is pending will cause an error. As with all errors, this can be examined in the LastError event or by using the LastError property.


ReadWrite
Reading and Writing with Sockets

In order to get a basic understanding of how sockets work, you should first read the section titled, An Introduction to TCP/IP Programming. For each of the SocketTools controls that allow direct reading and writing with the socket, there is a consistent and easy method by which the data can be transfered. The controls share the same properties, events and methods so that when you learn how to do this with one control, you are able to perform the same task with the others.

The properties involved in reading and writing directly with the socket connection are itemized in the following table.

•	IsReadable

This property returns True if data can be read from the socket without blocking. For non-blocking sockets, this property can be checked before the application attempts to read the socket, preventing an error.

•	RecvLen

Specifies the maximum number of bytes that may be returned by the RecvData property. After the data has been read, RecvLen contains the actual number of bytes that have been read.

•	RecvData

Returns data that has been read from the remote server, up to the number of bytes specified by the RecvLen property. If no data is available to be read, an error will be generated if the control is non-blocking. If the control is blocking, the program will stop until data is written by the server, or the connection is closed.

•	RecvNext

Returns the number of bytes that are available to be read from the server.

•	IsWritable

Returns True if data can be written to the socket without blocking. For non-blocking sockets, this property can be checked before the application attempts to write to the socket, preventing an error.

•	SendLen

Specifies the maximum number of bytes that may be written to the server. If this value is greater than the length of the string being sent, the value is ignored and all of the characters are sent. After data has been written to the server, this property is updated to reflect the actual number of bytes written.

•	SendData

The data in the string is written to the remote server. If the connection is buffered the data is copied to the send buffer and control immediately returns to the program. The maximum number of bytes written on the socket is determined by the SendLen property. After the data has been written, the SendLen property will be changed to reflect the number of bytes actually written to the socket.

The events related to the reading and writing of sockets are listed in the next table.

•	Read

Generated for non-blocking sockets when data is available to be read from the server.

•	Write

Generated for non-blocking sockets when data can be written to the server after a previous attempt failed because it would cause the control to block.

Some controls only allow reading, while some controls allow only writing, yet others allow both and then there are a couple that don't allow any direct reading or writing. If you didn't follow that, don't fret, the following table summarizes which controls allows which operation. In all cases where reading and/or writing are allowed, the Blocking property exists to control the blocking nature of the data transfer.

The SocketWrench/VB, Telnet and RLIB controls exclusively require the reading and writing of the socket connection directly to accomplish their respective tasks. The POP and SMTP e-mail controls only require direct socket I/O during the retrieval or sending of the body of the message. Other controls that allow either reading or writing of the socket are provided for convenience and do not require those operations in order to use their functionality as in the FTP control.

When dealing with non-blocking sockets, the retrieval of data from the host is done strictly through the control's Read event.


FTP
ST/FTP

File Transfer Utility

Introduction

The ST/FTP sample demonstrates the use of the FTP (File Transmission Protocol) control. The sample is a complete FTP application that allows you to transfer files in either direction, walk through a directory structure listing its contents, rename and delete files. Files may be transfered in either binary or text modes. For completeness, the sample also allows the same file and directory management on the local system.

Description

ST/FTP consists of two forms: FTPCON.FRM and FTPMAIN.FRM. The FTPCON.FRM module contains the form that requests the name (or address) of the host system as well as a user name and optional password. This is all that is needed to establish a connection with the FTP server. The connection form also offers the opportunity to change the FTP server's port number if it is different than the default. It would be highly unlikely that this would need to be changed.

Most of the relevant code is embodied in the subroutine ConnectBtn_Click, executed when the user clicks the Connect button.

  MainForm.FTPClient1.HostName = HostName.Text
  MainForm.FTPClient1.RemotePort = Port.Text
  MainForm.FTPClient1.Username = Username.Text
  MainForm.FTPClient1.Password = Password.Text
  MainForm.FTPClient1.Action = FTP_ACTION_CONNECT

Since the default value for the Blocking property is True, the form will be hidden only after the connection is completed, either successfully or with an error.

The FTPMAIN.FRM form module contains the interface to most of the FTP functions. Each file command that involves the remote system translates to a single FTP action value that can be assigned to the control's Action property. Here is a sample of a file transfer from the remote host to the local system.

Private Sub LocalFileCopy_Click ()
    FtpClient1.RemoteFile = Trim$(RemoteFile.Text)
    If Len(FtpClient1.RemoteFile) = 0 Then
        MsgBox "No remote file selected"
        Exit Sub
    End If

    FtpClient1.LocalFile = Trim$(LocalFile.Text)
    If Len(FtpClient1.LocalFile) = 0 Then
        FtpClient1.LocalFile = FtpClient1.RemoteFile
    End If

    If TextOption.Value Then
        FtpClient1.Binary = False
    Else
        FtpClient1.Binary = True
    End If

    Screen.MousePointer = 11 ' Hourglass
    FtpClient1.Action = FTP_ACTION_GETFILE
    Screen.MousePointer = 0 ' Default
    UpdateLocalLists
End Sub

This code validates properly selected filenames from the remote system and for the local system. The Binary property is set for the type of transfer and then the Action property gets set to FTP_ACTION_GETFILE to perform the actual transfer. Although the Blocking property is True for the operation, a Progress event is generated periodically to give you the opportunity to implement some type of progress meter or indicator.


Mail
ST/Mail

Send and Receive Electronic Mail

Introduction

The ST/Mail sample demonstrates the use of the SMTP and POP3 controls. The SMTP control is required in order to send electronic mail, while the POP3 control is required to receive electronic mail. ST/Mail is a complete application that manages a mailbox of incoming and outgoing e-mail, allows you to contruct a message for sending and retrieves e-mail from the server that you specify.

Description

ST/Mail includes five forms in order to create a general purpose mail application that manages a mailbox.

•	GETFORM.FRM

This form is used to retrieve messages form a POP3 server. This is the form that includes the POP3 control. During the form's Load event, a connection is made to the POP server to just get the number of messages that reside there. This is displayed on the form so that the user can decide whether to click on the Retrieve button to actually download the messages. The form also offers a check box as to whether the downloaded messages should be deleted from the POP server.

•	LISTFORM.FRM

This form is called on by the SendForm form to maintain a list of multiple addresses to send a message to for either the To field or the Cc field.

•	MAILFORM.FRM

This is the application's main form that utilizes the grid control to maintain the mailbox. The grid either displays the user's current outbox or inbox as selected by a menu option. Entries are added to the Inbox by retrieving messages or added to the Outbox by sending messages. Messages can be deleted from either box.

•	MLSETUP.FRM

This is the setup form that obtains information from the user for interaction with the SMTP and POP controls. The user must supply the hostnames of the POP and SMTP server (most likely the same), a username and optional password (for the POP control only) and the directory where mail messages should be stored (the mailbox).

•	SENDFORM.FRM

This is the module that handle the sending of e-mail messages. The SMTP control resides on this form. The form allows the user to enter a destination e-mail address, an e-mail address of the person sending the message, an optional cc address, a subject, an optional file attachment and the body of the message.

The code to retrieve messages from a POP server is contained in the subroutine RetrieveBtn_Click in the GETFORM.FRM form module. The POP_ACTION_GETMSG Action property value makes the request of the POP server to begin downloading a message based on the value of the Message property. At this point, the RecvData property is assigned to a string buffer to capture the body of the message. As each chunk is received the general subroutine ParseMessage is called to identify the components that is required for the structure of our mailbox. In addition, the ParseMessage routine is watching for a MIME encoded message for the purpose of extracting a possible file attachment.

The code to send messages to an SMTP server is contained in the general subroutine called SendCurrentMessage. The first action taken is SMTP_ACTION_ADDRESS to identify who the message is from as indicated by the Address property (taken from the From field). This lets the SMTP server know that a message is coming. Then a header to our message is constructed and sent first prior to the body of the message. Should a file attachment be specified, the necessary items are sent to create a MIME encoded message.


EAlarm
ST/E-Alarm

Take Action When E-Mail Arrives

Introduction

The ST/E-Alarm sample doesn't exploit many features of SocketTools but is included to address the nuisance of having to check your e-mail only to find none has arrived. ST/E-Alarm uses the POP3 control to simply make the connection to your POP server to determine if any new messages have arrived. This feature is wrapped by a Timer control so that this check is done automatically in an interval as defined by the user. You may add code to this sample to customize the way in which you want to notified when e-mail arrives. The sample as delivered was written to dial a person's beeper using the Communications control provided by Visual Basic.

Description

ST/E-Alarm uses SocketTools POP3 control to connect to a POP server to check for the extence of e-mail messages, the Visual Basic standard communications control to dial a numeric beeper sequence and the timer control is handle the setting and triggering of an alarm. The event generated by the timer control as a result of the user-defined interval activates the checking for e-mail.

Sub Timer1_Timer ()
    Dim nmesg As Integer
    Static prevnmesg As Integer

    AlarmValue = AlarmValue + 1
    If AlarmValue = AlarmInterval Then
        Timer1.Enabled = False
        nmesg = GetMessageCount()
        If nmesg = 1 Then
            AlarmForm.Caption = Format(nmesg) +
                " message waiting"
        ElseIf nmesg > 1 Then
            AlarmForm.Caption = Format(nmesg) +
                " messages waiting"
        Else
            AlarmForm.Caption = OrigCaption
        End If
        If nmesg > 0 And nmesg > prevnmesg Then
            TestBtn_Click 'Beeper alert if setup
        End If
        prevnmesg = nmesg
        AlarmValue = 0
        Timer1.Enabled = True
    End If
End Sub

The timer control's interval is set to one minute. Each time the Timer event is generated we need to first check if the user's alarm interval has been reached. If so, the function GetMessageCount() is called. This function returns the number of e-mail messages that is stored in our mailbox. The function connets, queries and disconnects from the POP server. After setting the application's caption to the number of messages waiting, we will perform the dialing code only if we have more messages than the last time we checked.

The GetMessageCount() function is the only place where the POP3 control is accessed.

Function GetMessageCount () As Integer
    POPClient1.HostName = Trim(PopServer.Text)
    If Len(POPClient1.HostName) = 0 Then Exit Function
    POPClient1.Username = Trim(Username.Text)
    If Len(POPClient1.Username) = 0 Then Exit Function
    POPClient1.Password = Trim(Password.Text)
    POPClient1.Action = POP_ACTION_CONNECT
    GetMessageCount = POPClient1.MessageCount
    POPClient1.Action = POP_ACTION_DISCONNECT
End Function

Before a connection can be established, the control must know which system to connect to, the name of the user's account that manages the mailbox and an optional password. This information would have been entered on the application's main form. The HostName property, as with all SocketTools components, is used to identify the system to connect to. The Username and Password properties identify the user's account information that manages the desired mailbox. Sandwiched between the POP_ACTION_CONNECT and POP_ACTION_DISCONNECT actions is the assignment of the MessageCount property to the function. That's all there is to it.

Although this sample is rather trivial, you could certainly take other actions when new e-mail arrives. You can use the POP_ACTION_GETHDR Action property to retrieve just the message header to assess who the message is from as well as the subject. Such an addition to this application would allow you to take different actions based on who's sending the message or what the message is about.


Netcheck
ST/Netcheck

Domain Name Lookups

Introduction

The ST/NetCheck sample demostrates the use of the DNS (Domain Name Server) client control as well as the ICMP (or Ping) control. You can lookup hosts by name, address or a range of addresses. The range option is handy when looking to identify all of the connected systems within an Intranet. Once a system is located, you can use the ping option to further test its existence.

Description

The basic concept of the ST/Netcheck application is to manage various host systems in a grid control to view their DNS information. Entries can be added into the grid using the New option under the File menu. Once selected, you have three options by which a host can be checked: by hostname, host address or a range of addresses. This last option gives an ability to determine what systems within a domain have been authenticated by a DNS server. Once the grid contains systems that were checked, the Ping menu option can be used to actually validate a proper network connection.


Newsreader
ST/Newsreader

Read, Download and Post Newsgroup Articles

Introduction

The ST/Newsreader sample demonstrates the use of the NNTP (Network News Tranfer Protocol) client control. The newsreader supports making the connection to a network news server, subscribing to newgroups, downloading of articles and the posting of articles.

Description

The ST/Newsreader application demonstrates many of the actions that the NNTP control supports. The following modules are included in the newsreader sample:

•	POSTFORM.FRM

This form is used to post articles into specified newsgroups. The form's code demonstartes the use of the NNTP_ACTION_NEWARTICLE and NNTP_ACTION_POSTARTICLE Action property values. The user enters a From address, Newsgroups, the Subject and the body of the article. This information is collected and posted to the newsgroups upon the click of the Post button.

•	STNEWS.FRM

The main form for the newsreader application uses a grid control on the left half of the form and a listbox on the right half. The listbox maintains a list of newgroups while the grid control lists the articles that reside within the selected newsgroup. Selecting an article in the grid followed by a click on the Read button, will cause the grid and listbox to be replaced by a large text box displaying the contents of the article. The Next and Prev buttons will travers the article list without having to go back to the grid. This module demonstrates the use of the NNTP_ACTION_LISTGROUPS, NNTP_ACTION_LISTARTICLES and NNTP_ACTION_GETARTICLE Action property values.

•	SUBSCRIB.FRM

This form very simply request the name of a newsgroup to subscribe to. The actual code to perform this function is in the NewsSubscribe_Click subroutine in the STNEWS.FRM form module.


Telnet
ST/Telnet

SocketTools Telnet Application

Introduction

The ST/Telnet sample demonstrates the use of the Telnet control and the Terminal Emulation control. Basic Telnet functionality is demonstrated to connect to a remote host using the terminal emulation control to process the transfer of data between user and host computer.

Description

A simple form, TNCNCT.FRM is used to get the host name (or address) of a system to connect to that must be running a telnet server. The STELNET.FRM form module contains the Telent client control as well as the SocketTools emulation control that offers basic ANSI terminal emulation. The Telnet control, being separate, does not require the emulation control to function. Telnet connections can be established with hosts and simply utilize SendData and RecvData to interact with the server without "seeing" the data.

Since this is a general purpose telnet sample to act as a terminal, Blocking is set to False in order to monitor Read events that are generated any time the telnet server has data for the client.

Sub TelnetClient1_Read (DataLength As Integer)
    TelnetClient1.RecvLen = DataLength
    Terminal1.Output = TelnetClient1.RecvData
End Sub

This is the entire event subroutine. The data received from the host is assigned to the Output property of the emulation control for display.

Data is sent to the server each time the user presses a key. This happens by the Keypress event for the Terminal control.

Sub Terminal1_KeyPress (KeyAscii As Integer)
    TelnetClient1.SendLen = 1
    TelnetClient1.SendData = Chr$(KeyAscii)
End Sub


Web
ST/Web Browser

SocketTools Internet and Intranet Web Browser

Introduction

The ST/Web sample is SocketTools version of a World Wide Web browser. The web browser sample exploits the use of the Web control which automatically loads and utilitizes the image rendering control. ST/Web is able to view web documents as specified by a URL on remote or local systems, allows the viewing and saving of the HTML source, and lists all of the links that a document page uses in a listbox control.

Description

The SocketTools Web Browser application provides complete browsing capability where virtually all of the work is done by the control. The following code is activited once the user enters a URL and either presses the Enter key or selects the Open option under the File menu.

  WebClient1.Resource = Resource
  WebClient1.Action = WEB_ACTION_LOAD ' Load document

This activates the control to make the necessary host connections to retrieve documents, cache them, retrieve other resource the page may contain and render them in the area defined by the programmer. Should the user click on an anchor that is on a web page, the control continues to retrieve resources and display them.

The power and flexiblity in the Web control is the generation of events during the entire process. The main form that includes the Web client control highlights the use of the following events:

•	Connect

•	Disconnect

•	Execute

•	Load

•	MouseMove

•	Preview

•	Progress

•	QueryLoad

The code in the Execute event attempts to launch an associated application for a resource file that is not normally supported. This can be used to automatically download or view a file.

The Load event is generated after the control has loaded the resource for viewing. This event is used by the browser to updated the status line for the current resource being viewed.

The MouseMove event is generated when the user places the mouse over an anchor. The ActiveLink and Link properties can be used to determine which anchor the mouse is pointing to.

The Preview event is generated immediately after a resource has been brought over to the local system and just before rendering would occur. This event presents an opportunity to add, modify or delete items from the HTML file if that is the current resource type. The event passes the filename of the cached resource as an argument to the subroutine.

The Progress event is generated periodically during the download of resources. This gives the opportunity to display either a progress guage or percentage during each download.

The QueryLoad event is generated just after an anchor is selected. This gives the programmer the flexiblity to control what the users selects. The URL can be changed or access can be denied to various sites.


Fantan
Internet Fantan

Multi-player Internet-enabled Card Game

Introduction

Fantan is a simple card game that requires up to four players. The focus of the game for development purposes is that it was written using the SocketWrench/VB control to allow the players to play over the Internet. The Fantan game is written in Visual Basic and uses the CARDS.DLL that is used by Solitaire and Hearts. Fantan employs the client/server model where the process that starts out as the dealer is always the server and players that join the dealer's game are the clients. Both client and server logic are embodied in the same program.

Most of the Fantan application has to do with playing the game. There's logic to handle computer play, drawing routines, keeping score, etc. The following sections will focus on the adaptation of the Internet to the application. It may be worthy to note that the networking code was adapted after the game engine and play was written. Thus, this is an example of taking an existing, working application and network enabling it after the fact. The focus of the following sections will describe the network design strategy that was used, how the server code and client code were implemented and a section on the communication between the client and server.

Network Design

Since this is an application that creates its own protocol, the only control that is used is SocketWrench/VB. The first order of business is to establish how many sockets are required and their intended use. One socket is required for the “server” that will first use the SOCKET_LISTEN Action property in order to wait for other players to join the game. Then, we will need a socket for each client. Since we can have up to three players join a game and when we include the socket for the server, we need to plan for four sockets.

Rather than creating four separate socket controls on the main form, a control array would be more appropriate. The same code would be executed for each of the sockets that interact with the clients that it makes sense to use the array to encapsulate that code and simply index the socket array based on the client that is at issue. Fantan also uses a Visual Basic technique to dynamically create the socket array rather than pre-creating an array of four sockets. Although it was not completely necessary for Fantan, the technique is demonstrated for situations where a general purpose server would not necessarily know how many client would require the server's services.

The first step is to load the SocketWrench/VB control onto the form and set it up as a control array. This is done by placing two SocketWrench/VB controls onto the form with the same value for their Name property. When naming the second SocketWrench/VB control “Socket1”, Visual Basic will ask if a control array is desired. Clicking “Yes” will rename the controls Socket1(0) and Socket(1). To establish the control array with just the one control, simply delete the control named Socket(1). Programmatically, we can use the Visual Basic Load statement to dynamically add more sockets to the array.

Server Setup

Like any well-behaved server, once the user decides to be a dealer and wishes to invite other players, the socket that was loaded onto the form needs to be initialized and then begin “listening” for potential clients. Here's the code which is encapsulated in the Server_Setup subroutine in the FANTAN.FRM module:

  Sock(0).AddressFamily = AF_INET
  Sock(0).Protocol = IPPROTO_IP
  Sock(0).Type = SOCK_STREAM
  Sock(0).Binary = True
  Sock(0).Blocking = False
  Sock(0).LocalPort = IPPORT_FANTAN
  Sock(0).Action = SOCKET_LISTEN

The SOCKET_LISTEN Action is non-blocking in that execution of the Visual Basic program continues. The rest of the Server_Setup subroutine displays a message notifying the dealer that we are waiting for players giving the dealer the option to press F2 when the desired players have joined the game.

The SOCKET_LISTEN Action property is ultimately what puts an application in the role of a server. The application is stating, “I have a service to provide, if you're interested, connect to the system I am on with my port number.” The IPPORT_FANTAN is a constant that was created by the Fantan program which was chosen to be a unique value. A socket connection is completed when a client first finds the system that the server is on and when the port number requested by the client matches a port number currently in a listening state is found on that system.

At this point only two things can happen. Either the user presses the F2 key which will cause play to continue with the players that have joined thus far or the SocketWrench/VB Accept event will be generated. This event is caused by a client that has made the request of our Fantan service, or, in other words, has selected the option to join our game.

Accepting a connection requires the creation of a dedicated socket for subsequent communication with that client. We will need to keep the original socket open and in a state of “listening” so that we can solicit more players. Here is the code to accomplish this:

  LastSocket = LastSocket + 1
  i = LastSocket
  Load Sock(i)	'Dynamically allocate a new socket

  Sock(i).AddressFamily = AF_INET
  Sock(i).Protocol = IPPROTO_IP
  Sock(i).Type = SOCK_STREAM
  Sock(i).Binary = True
  Sock(i).BufferSize = 1024
  Sock(i).Blocking = False
  Sock(i).Accept = SocketId

The LastSocket variable keeps track of the number of sockets in our control array. The Load statement allocates the new socket and adds it to the array. The intialization of the socket properties are consistent with what the server is expecting. Once the SocketId, which is passed to the Accept event, is assigned to the Accept property, the socket connection is completed. The server instance of the Fantan application now has two sockets: Sock(0) continues to execute the listen action waiting for new clients and Sock(1) is now dedicated to communicating with our first player that has joined the game.

Client Setup

Fantan's initial dialog box presents three options: play the game by yourself (no network involved), be the initial dealer and invite new players to join your game or join a game which someone else has already decided to be the dealer. By joining a game, we are accepting a role as a client. As part of the role of a client, we need to introduce ourselves to the server by first locating it and then negotiating the terms by which we will communicate. Joining a game that has been started by another user will require either the name or IP address of the dealer's system. When the dealer initiates their game, this information is displayed so that other player's can use that information to when joining.

After having selection the join option and having entered a host name or address, the click of the OK button causes the code in the Client_Setup subroutine to be executed. This code snipet is found in the FANTAN.FRM module:

  Sock(0).AddressFamily = AF_INET
  Sock(0).Protocol = IPPROTO_IP
  Sock(0).Type = SOCK_STREAM
  Sock(0).Binary = True
  Sock(0).BufferSize = 1024
  Sock(0).Blocking = False
  Sock(0).HostName = NetHostSystem
  Sock(0).RemotePort = IPPORT_FANTAN
  Sock(0).Action = SOCKET_CONNECT

This code by the client initializes some of the socket properties in perparation for making a connection with the server. It is important to match the expected properties of the server such as AddressFamily, Protocol, Type and especially RemotePort. The NetHostSystem variable is a global variable that contains the value of the text box from the initial dialog box where the user entered a host name or address of where the dealer is located. Note that the HostName property can be assigned an IP address.

The Action property assignment, using the SOCKET_CONNECT constant, is a blocking operation that actually does the work of locating the server and authenticating the connection. Errors can occur such as not be able to locate the host system (by name or address) or that the port number was not located at the host. The SOCKET_CONNECT action is what causes the Accept event to be generated on the server side. It is now up to the server to complete the socket connection. If successful, the client has a direct connection to the server using the socket Sock(0).

Communicating Over the Socket

There are two issues to discuss in this section. There's the actual mechanics of communicating over the socket and there's the issue of what are we communicating over the socket. First, some preliminaries.

All of the communications between client and server are utilizing non-blocking sockets. For the most part, with respect to the coding of the application, this affects the receiving of data. The sending of data is not impacted since the code's logic is not impacted. When data needs to be sent we call the subroutine Client_Send or Server_Send. Receiving data, on the other hand, is strictly done through the socket control's Read event; a byproduct of using non-blocking sockets. This is done so as to not impact the user's interaction with the application while the server notifies the clients of various activity or vice versa.

For the server's role in communicating with the clients, there are times where the server will only need to send data to one client and other times where the same data would need to be broadcast to all connected clients. The dealer's sending subroutines are encapsulated in Client_Send and Broadcast (which calls Client_Send for all connected clients). There are messages received from clients that are processed by the server. Most messages received by the server will need to be broadcast to all of the other clients.

A client, as opposed to the server, need be only concerned about itself. Messages are sent to or received from the server without regard to other clients. This is the fundamental role of the Fantan game server; to treat each client independently while synchronizing activity between all connected clients. For example, when a client plays a card, a message is sent to the server identifying the client and the card played. In order for the other clients to update their windows, the server assumes the responsiblity for broadcasting the clients message to the other clients.

First, we'll address the sending code. The first code fragment is the Client_Send subroutine in the FANTAN.FRM module which is called anytime the server needs to send a client a message.

Sub Client_Send (xs As Integer, sbuf As String)
    Dim xbuf As String

    If Sock(xs).Connected Then
        xbuf = sbuf + NL
        Sock(xs).SendLen = Len(xbuf)
        Sock(xs).SendData = xbuf
    End If
End Sub

The outside If statement is used to validate that the socket is still with us so as to not generate an error. The message is then delimited with a newline (the NL constant) so as to give the message parser the means to isolate a single message from the stream. Assignment to the SendLen and SendData properties are required to place the data onto the socket.

The Client_Send subroutine is called from the Broadcast subroutine to send a message to call of the connected clients.

Sub Broadcast (sbuf As String)
    Dim xs As Integer

    For xs = 1 To LastSocket
        Client_Send xs, sbuf
    Next xs
End Sub

Since we know that Client_Send validates the socket, we can call it without much concern for error. When the client needs to send a message to the server, a call is made to the Server_Send subroutine.

Sub Server_Send (sbuf As String)
    Dim xbuf As String

    xbuf = sbuf + NL
    Sock(0).SendLen = Len(xbuf)
    Sock(0).SendData = xbuf
End Sub

As you can see, sending data is the same regardless of the role of the application. We still add a delimeter to the message and the client always references Sock(0) as the socket that is connected to the server.

Before we examine the receiving of data through the socket, we'll briefly discuss the messaging scheme that Fantan uses. Fantan messages need to communicate all of the activities that occur in a Fantan game. For example, the server must manage the shuffling of the deck before the start of each game. There are several ways to do this. We could have created a message to set the random number generator's seed to a specific value so that each client generates the same shuffled deck. We could go ahead and not only shuffle the deck but deal out the deck and then send a message to each client of what cards they hold. The determining factor is not so much what's best for network efficiency and performance but mostly the application itself. Remember, the Fantan game engine has already been developed and we are network enabling the existing code. As a result, the method that provides the simplest change that keeps the code very clean is to allow the server to shuffle the deck and broadcast a message to all clients that includes the order of the cards in the deck. The psuedo-code for this logic is as follows:

  If  we are the client  Then
    Get for the deck from the server
    Create the deck as if we had shuffled it
  Else
    Shuffle deck
    if  we are the server  then
      Broadcast deck to all clients
    End If
  End If
  Deal the cards

Each message was designed to follow a consistent format. Each activity forms a message that contains a 2-letter code followed by an optional numeric argument (usually the client identifier) followed by an optional string argument. Here are some samples messages:

NM 2 Brad	Player #2's name is Brad
GS	Ready to play with current players
GO 3	Player #3 has left the game
PL 2 410	Player #2 plays the 10 of clubs
PS 4	Player #4 passes

Message parsing occurs in the subroutine GetNetCode found in the FANTAN.BAS module. This subroutine sets the global variables NetNumArg and NetStrArg if they exist. NetNumArg will contain the value of the numeric value associated with the message and NetStrArg will contain the value of any argument that follows the message code.

On the issue of receiving data from the socket, a few assumptions have to be dispelled. First, you can never assume when a message will be received. With a non-blocking socket, we know our application will receive a Read event for the socket control. By the very nature of events, we are unable to plan for them. Also, we cannot make any assumption on the contents of what is received. For example, if the server sends three messages with three separate calls to Client_Send, the client may receive them with one, two or three Read events generated. This is the reason for using a message delimeter such as the newline or line feed character (Chr$(10)).

Rather than process any messages during the actual Read event for the socket, the Fantan application uses a message buffer which the Read event adds to for each time the event is generated. Here is the code for the socket's Read event:

Sub Sock_Read (Index As Integer, DataLength As Integer,
               IsUrgent As Integer)
    Dim rbuf As String

    Sock(Index).RecvLen = DataLength
    rbuf = Sock(Index).RecvData
    gMsgBuf = gMsgBuf + rbuf
End Sub

The data is received and temporarily stored in rbuf which is simply appended to our message buffer held by the variable gMsgBuf. The flag variable fRecv is set to True to indicate that a message has arrived. Note that although the server can expect a message for any one of three clients which would be indicated by different values for the Index variable, that we still append the messages to a single message buffer. This is because we have designed the messages to include which client has sent the message if it is important for the server to know.

At various places in the code, it is either necessary to execute game playing code or to fetch a message. For the server, messages are expected from the client when it is that remote players turn. For clients, a messsage is expected any time it is not our turn. There is also messages that are expected to start the game as well as end the game. In all cases where a message is expected, the subroutine WaitForMessage is called.

Sub WaitForMessage ()
    Do
        DoEvents
    Loop Until Len(gMsgBuf) > 0
    GetMessage
End Sub

Very simply, as long as our message buffer is empty, we call DoEvents waiting for the socket's Read event to be executed. Once we get the message, it is parsed in the GetMessage subroutine.

The heart of the game playing engine is the subroutine CheckAutoPlay. This subroutine embodies code that is executed in the case when we are the server, the client or not involved in a network game.

Sub CheckAutoPlay ()
    Dim xp As Integer
    Dim xcard As Integer
    Dim winner As Integer
    Dim sbuf As String

    winner = False
    Do While IsComputerPlayer(xCurPlayer) Or
             IsRemotePlayer(xCurPlayer)
        If IsComputerPlayer(xCurPlayer) Then
            xcard = BestCard(xCurPlayer)
            xp = xCurPlayer
            If xcard = 0 Then
                PlayPass
                sbuf = NS_PASS + " " + Format$(xp)
            Else
                PlayCard xCurPlayer, xcard
                sbuf = NS_PLAYCARD + " " + Format$(xp) +
                       ” ” + StrCard(xp, xcard)
            End If
            If fNetType = NT_SERVER Then
                Broadcast sbuf
            End If
        ElseIf IsRemotePlayer(xCurPlayer) Then
            StatusMsg.Caption = "Waiting for " +
                  GetPlayerName(xCurPlayer) + "..."
            StatusMsg.Enabled = True
            StatusMsg.Visible = True
            DoEvents
            Do
                WaitForMessage 'wait for card to be played
            Loop While NetNumArg = MyNetID()
            StatusMsg.Visible = False
            StatusMsg.Enabled = False
            DoEvents
            xp = xCurPlayer
            If NetMessage = NC_PASS Then
                PlayPass
                If fNetType = NT_SERVER Then
                    sbuf = NS_PASS + " " + Format$(xp)
                    Broadcast sbuf
                End If
            Else
                xcard = CardIndex(xCurPlayer, NetStrArg)
                PlayCard xCurPlayer, xcard
                If fNetType = NT_SERVER Then
                    sbuf = NS_PLAYCARD + " " + Format$(xp)
                           + " " + StrCard(xp, xcard)
                    Broadcast sbuf
                End If
            End If
        End If
        PlayerName(xp - 1).ForeColor = BLACK
        If CardsToPlay(xp) = 0 Then
            winner = True
            Exit Do
        End If
        PlayerName(xCurPlayer - 1).ForeColor = YELLOW
    Loop

    If winner Then
        DoWinStuff
        Fantan_Start
    End If

End Sub

The main While loop executes when it is not our turn. Once it is our turn, we need to wait for a mouse click for the selection of card or the click of the Pass button. If we are not playing a network game, then the three other players are computer players and no network related code is executed. The fNetType variable will not equal NT_SERVER or NT_CLIENT.

Since this subroutine is called only when it is not our turn, all other players are considered remote players to the client. The code shows what you would expect. We need to wait (or get) a message from the server to tell us what card was played or that the player passed and then go ahead and execute the code to that effect.

Client Cleanup

The client terminates the application in one of two ways. Either the client initiates the action to exit the program or a message is received from the server that the game is over. In the case of the client choosing the menu option to exit, a message is sent to the server that we are quitting. This allows the server to aknowledge the loss of a remote player and change it's status to a computer player. No sense penalizing the other players who are staying in the game.

Clicking the exit menu option causes this code to be executed:

If fNetType = NT_CLIENT Then
    Server_Send NS_GAMEOVER + " " + Format$(MyNetID())
    WaitForMessage
End If

The server is notified that the client wants out and then we wait for the server to aknowledge our message. The server, meanwhile, receives the game over message and processes it as follows:

Case NC_GAMEOVER 'client wants to quit
    'Index tells us who's quitting; let computer take over
    Client_Send NetNumArg - 1, NS_OK
    MsgBox GetPlayerName(NetNumArg) +
           " has left the game." + CRLF +
           "Computer is taking over.", 64
    SetModeToComputer NetNumArg
    PlayerName(NetNumArg - 1).Caption =
           GetPlayerName(NetNumArg)

This code is found in the GetMessage subroutine in the FANTAN.FRM module. We immediately send the aknowledgement back to the client and reset the player's mode from a remote player to a computer player.

Sub Client_Cleanup ()
    If Sock(0).Connected Then
        Sock(0).Action = SOCKET_CLOSE
    End If
End Sub

The only cleanup that needs to be done is to set the socket's Action property to SOCKET_CLOSE.
This action generates a Close event on the server's socket control which gives the server the opportunity to close its end of the socket. The following code is execute by the server upon the receipt of the Close event:

Sub Sock_Close (Index As Integer)
    'this sets the Connected property to False
    Sock(Index).Action = SOCKET_CLOSE
End Sub

The Index variable identifies the socket which is connected to the client that closed its end of the socket.

Server Cleanup

When the player that controls the server exits the application, a message is broadcast to all of the clients that the game is over. This gives the clients the opportunity to close their connections cleanly and then exit. The server waits for aknowledgement from the clients that the exit message has been received and dealt with. This code is found in ExitOption_Click in the FANTAN.FRM module.

If fNetType = NT_SERVER Then
    Broadcast NS_GAMEOVER
    For xs = 1 To LastSocket
        If Sock(xs).Connected Then
            WaitForMessage
        End If
    Next xs
End If

Once the server has received aknowledgements from all of the clients, the server does its socket cleanup and exits.

Sub Server_Cleanup ()
    Dim i As Integer

    If Sock(0).Listening Then
        Sock(0).Action = SOCKET_CLOSE
    End If

    For i = 1 To LastSocket
        If Sock(i).Connected Then
            Sock(i).Action = SOCKET_CLOSE
        End If
    Next i

End Sub

We first close the socket that had been listening for new players to join the game. This is followed by the code that closes all of the client connections.